package fms.controller;

import fms.dao.Grib2InfoService;
import fms.model.Grib2WdInfo;
import fms.util.*;
import org.apache.log4j.Logger;

import java.io.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * <P>FileName: Grib2BySkyviewParseCon.java
 *
 * @param
 * <P>Description: 解析Grib2风温数据
 * <P>Version: v1.0
 */
public class Grib2BySkyviewParseCon  {

    private static Logger logger = Logger.getLogger(Grib2BySkyviewParseCon.class);

    // 存入GRIB2_WD_INFO表的记录列表数据
    private ArrayList grib2InfoList = new ArrayList();

    // 提供数据库交互服务的对象
    private Grib2InfoService grib2InfoService = null;

    // 线程池,设置固定10个线程（入库线程数与数据库连接数对应）
    // 因为，每个入库线程有一个SaveGrib2DataThread对象，每一个SaveGrib2DataThread对象中有个grib2InfoService，grib2InfoService就是代表数据库连接
    //ExecutorService pool = Executors.newFixedThreadPool(3);

    // 以1.25度划分的全球坐标点总个数为 145(纬) * 288(经) = 41760
    private final int iCoordinatesSum = 41760;

    // 扫描目录根目录
    private String scanRootDir = "";
    // 公共目录名
    private final String publicDir = "us_avn";
    // 获取风温源数据目录
    private final String tempDirPath = "temper";
    // 获取风UV分量源数据目录
    private final String uvDirPath = "uv";

    // 系统仅录入有效时长为6、12、18的Grib2数据文件
    private final String regexGrib2VaildTime = "\\d+\\.0(06|12|18)"; // 06|12|18
    // 系统录入的有效时段种类的数量
    private final int iValidDurationNum = 3;

    // 解析失败文件的备份目录
    private String backupDirPath = "";
    // 解析成功文件的备份目录
    private String backupGrib2SuccessDir = "";

    private static StringBuffer sb = new StringBuffer();

    public Grib2BySkyviewParseCon(String grib2BySkyviewDirPath, String backupPath, String backupGrib2SuccessPath) {
        scanRootDir = grib2BySkyviewDirPath;
        backupDirPath = backupPath;
        backupGrib2SuccessDir = backupGrib2SuccessPath;

        grib2InfoService = ConfUtil.getSingletonApplicationContextDao().getBean("grib2InfoService", Grib2InfoService.class);
    }

    // Grib2解析入库程序在解析数据入库前，验证指定日期、指定时效、指定高度层的全球经纬度风温信息是否完整；不完整不入库
    // 入库线程启动时，先验证数据是否存在且完整，不存在则入库；存在不完整则删除再入库
    // 1天的6/12/18小时预报，开发环境测试入库时间约10分钟
    public boolean parseDataSource() {
//        System.out.println("11111111111111111111111111\n");
//        boolean ex = grib2InfoService.exists(251036154);
//        if (null != grib2InfoService) {
//            System.out.println("2222222222222222222");
//        }
//        long ex = grib2InfoService.count();
//        grib2InfoService.countByTimeAlt("2014-11-01 06:00:00", "2014-11-01 12:00:00", 100);
        // 注意：6点不能写成6，要写06，因为SQL是比较字符串
//        long ex = grib2InfoService.deleteByTimeAlt("2014-11-01 06:00:00", "2014-11-01 12:00:00", 150);
//                System.out.println(ex);

        try {
            logger.info("本次Grib2数据解析线程已开始" + Thread.currentThread() + DateUtil.getCurrentDateTime(SysConstants.FORMAT_DATETIME_FULL));
            BufferedReader bufReader = null;
            File fSacnDir = new File(scanRootDir);
            if ( (null == fSacnDir) || !fSacnDir.isDirectory() ) {
                logger.error("无法打开指定数据源(风温or风分量)目录：" + scanRootDir);
                return false;
            }

            // 循环遍历日期目录
            String[] dateDirNameList = fSacnDir.list();
            for (int i = 0; i < dateDirNameList.length; i++) {
                logger.debug("扫描目录：" + dateDirNameList[i]);
                if ( isNeedScanDir(dateDirNameList[i]) < 0 ){
                    continue;
                }
                logger.debug("扫描有效目录：" + dateDirNameList[i]);
                // 每个高度层目录要以“有效时段种类的数量”为遍历次数，每次处理一个有效时段的数据
                for (int iCount = 0; iCount < iValidDurationNum; iCount++) {
                    logger.debug("已处理 " + iCount + " 份时效数据！");
                    // 由于SKYVIEW使用的Grib2数据（风温、风分量）分别放置在两个不同目录，且这两个目录下存放着高度层目录列表，
                    // 高度层目录中存放着风数据
                    // 遍历标准气压高度层列表，按高度层来读取风数据，逐高度层入库
                    // 目录中，有数据文件，则入库并删除；无数据，则略过
                    for (int iAtomsNum = 0; iAtomsNum < GribUtil.atomsList.size(); iAtomsNum++) {
                        String pathTemp = scanRootDir + File.separator + dateDirNameList[i] + File.separator + publicDir
                                + File.separator + tempDirPath + File.separator + GribUtil.atomsList.get(iAtomsNum);
                        String pathUV = scanRootDir + File.separator + dateDirNameList[i] + File.separator + publicDir
                                + File.separator + uvDirPath + File.separator + GribUtil.atomsList.get(iAtomsNum);
                        getAndStoreOnePieceData(pathTemp, pathUV, GribUtil.atomsList.get(iAtomsNum));
                    }   // for (int i = 0; i < GribUtil.atomsList.size(); i++) {
                }

            } // for (int i = 0; i < fileNameList.length; i++) {
            logger.info("本次Grib2数据解析线程已结束" + Thread.currentThread() + DateUtil.getCurrentDateTime(SysConstants.FORMAT_DATETIME_FULL));
        } catch (Exception e) {
            logger.error("系统崩溃了");
            logger.error( e.getMessage() );
            return false;
        }
        return true;
    }

    /**
     * 保存grib2数据线程
     */
    private static class SaveGrib2DataThread implements Runnable {
        private List list;
        private Grib2InfoService grib2InfoService;
        private Grib2TimeVo grib2TimeVo;
        private int iCurAtoms;
        // 入库程序每次入库时先要查询数据库中是否存在指定时段、指定高度层的全球经纬度风温数据的存在与完整性
        private final int perStoreRecorderNum = 125280;

        public SaveGrib2DataThread(Grib2InfoService grib2InfoService, List dataList, Grib2TimeVo grib2TimeVo, final int iCurAtoms) {
            this.grib2InfoService = grib2InfoService;
            this.list = dataList;
            this.grib2TimeVo = grib2TimeVo;
            this.iCurAtoms = iCurAtoms;
        }

        @Override
        public void run() {

            logger.info("(开始)入库线程名:" + Thread.currentThread() + DateUtil.getCurrentDateTime(SysConstants.FORMAT_DATETIME_FULL));
            long hasRecordNum = grib2InfoService.countByTimeAlt(grib2TimeVo.getPublicTime(), grib2TimeVo.getEndTime(), iCurAtoms);

            if (0 == hasRecordNum) {
                //保存grib2数据
                grib2InfoService.save(list);
                logger.debug("本次入库数据，库中不存在，入库" + list.size() + "条");
            } else if (perStoreRecorderNum != hasRecordNum) {
                // 删除该时段、该高度层的不合法风温数据
                long delSize = grib2InfoService.deleteByTimeAlt(grib2TimeVo.getPublicTime(), grib2TimeVo.getEndTime(), iCurAtoms);
                grib2InfoService.save(list);
                logger.debug("本次入库数据，库中不完整（多or少），已删除" + delSize + "条; 已入库" + list.size() + "条" );
            } else if (perStoreRecorderNum == hasRecordNum) {
                // 不用进行任何操作
                logger.debug("本次入库数据，库中已存在，不执行入库操作" );
            }
            logger.debug("(结束)入库线程名:" + Thread.currentThread() + DateUtil.getCurrentDateTime(SysConstants.FORMAT_DATETIME_FULL));
        }
    }

    /**
     * Function : 将指定日期、指定高度层、一个时效（6 or 12 or 18）的一组Grib2（对应的“风温、UV分量”）数据入库
     * param dirName : 以yyyyMMddHH表示的，时间字符串
     */
    private void getAndStoreOnePieceData(final String pathTemp, final String pathUV, final int iCurAtoms)
            throws Exception {
        String[] arrGlobalPointTemp = null;
        String[] arrGlobalPointUV = null;
        // 处理一个存放温度分量的Grib2数据文件
        Grib2BySkyviewFileVo grib2TempVo = null;
        // 处理一个存放UV分量的Grib2数据文件
        Grib2BySkyviewFileVo grib2UVVo = null;

        logger.debug("  处理目录：" + pathTemp);
        logger.debug("  处理目录：" + pathUV);
        grib2TempVo = GetGrib2FileObj(pathTemp, 3);
        if (null != grib2TempVo) {
            // 将经纬度点的温度数据存入数组
            arrGlobalPointTemp = grib2TempVo.getGrib2Context().split(" +");
            logger.debug("  当前风温文件含有温度数据个数：" + arrGlobalPointTemp.length);
        }

        grib2UVVo = GetGrib2FileObj(pathUV, 3);
        if (null != grib2UVVo) {
            // 将经纬度点的UV分量数据存入数组;
            // 根据SKYVIEW的Grib2数据说明，数组前半部分为全球经纬度点U分量，后半部分为V分量
            arrGlobalPointUV = grib2UVVo.getGrib2Context().split(" +");
            logger.debug("  当前风温文件含有UV数据个数：" + arrGlobalPointUV.length);
        }

        // 没有解析到（同一高度层、同一起止日期）一组（温度、UV分量）数据 ==> 防止指定高度层、经纬度点风温数据不完整
        if ( (null == grib2TempVo)  || (null == grib2UVVo)
                || ( (null != grib2TempVo) && (iCoordinatesSum != arrGlobalPointTemp.length))
                || ( (null != grib2UVVo) && (iCoordinatesSum * 2 != arrGlobalPointUV.length)) ) {
            logger.error("  grib2TempVo：" + grib2TempVo);
            logger.error("  grib2UVVo：" + grib2UVVo);
            return;
        }

        /*
        * 值与经纬度的匹配算法说明：
        *  从SKYVIEW项目组获取到的Grib2数据格式会影响，经纬度点与数据的对应关系（对于SKYFMS1期基于气象局的Grib2数据解析服务实现而言）
        *  年 月 日 时 时效  高度层(mb) 经度格距  纬度格距  起始经度  终止经度  起始纬度  终止纬度（均为浮点数）
        *  14 10 22 18 006    100       1.250000 -1.250000   0.00      358.75    90.00    -90.00
        *  由上面数据说明可知：数据依次为
        *     纬度
        *  -90 |
        *      |
        *      |
        *  90  |
        *      ---------------------------------------  经度
        *    0            180   -178.75            -1.25    (刘佳修正)
        *    先变经度（以1.25为增量）、再变纬度（以-1.25为增量）
        *    eg：第一个数据表示 (0, 90)；第二个点数据表示（1.25, 90）.....
        *
        *    注意：下面for循环中涉及的数值，是根据数据源文件中的头两行说明得来的
        * */
        int iCoordinatesPointNum = 0;
        // 以1.25为间隔，遍历纬度范围
        for (double lat = 90; lat >= -90; lat -= 1.25) {
//            logger.debug("当前纬度为：" + lat + "  已处理点数量：" + iCoordinatesPointNum );
            // 以1.25为间隔，遍历经度度范围
            for (double lon = 0; lon <= 180; lon += 1.25) {
                //                            System.out.println("lat:" + lat);
                //                            System.out.println("lon:" + lon);
                // 第二个条件：数据不完整不入库，且避免数组越界
//                if ((null != grib2TempVo) && (iCoordinatesSum == arrGlobalPointTemp.length)) {
//                    logger.debug("当前处理的文件为：" + pathTemp + "\\" + grib2TempVo.getGrib2Time().getPublicTime() + "." + grib2TempVo.getGrib2Time().getValidDuration());
                    Grib2WdInfo grib2WdTmp = new Grib2WdInfo();
                    //开始时间
                    grib2WdTmp.setStartTime(DateUtil.StrToDate(grib2TempVo.getGrib2Time().getPublicTime(), SysConstants.FORMAT_DATETIME_FULL));
                    //结束时间
                    grib2WdTmp.setEndTime(DateUtil.StrToDate(grib2TempVo.getGrib2Time().getEndTime(), SysConstants.FORMAT_DATETIME_FULL));
                    //大气压强
                    grib2WdTmp.setAtmosPressure(new BigInteger( String.valueOf(iCurAtoms)) );
                    //经度
                    grib2WdTmp.setLon(new BigDecimal(lon));
                    //纬度
                    grib2WdTmp.setLat(new BigDecimal(lat));
                    grib2WdTmp.setWindType("TMP");
                    grib2WdTmp.setWindVel(new BigDecimal(arrGlobalPointTemp[iCoordinatesPointNum]));

                    grib2InfoList.add(grib2WdTmp);
//                }
                // 第二个条件：数据不完整不入库，且避免数组越界
//                if ((null != grib2UVVo) && (iCoordinatesSum * 2 == arrGlobalPointUV.length)) {
//                    logger.debug("当前处理的文件为：" + pathTemp + "\\" + grib2UVVo.getGrib2Time().getPublicTime() + "." + grib2UVVo.getGrib2Time().getValidDuration());
                    Grib2WdInfo grib2WdU = new Grib2WdInfo();
                    //开始时间
                    grib2WdU.setStartTime(DateUtil.StrToDate(grib2UVVo.getGrib2Time().getPublicTime(), SysConstants.FORMAT_DATETIME_FULL));
                    //结束时间
                    grib2WdU.setEndTime(DateUtil.StrToDate(grib2UVVo.getGrib2Time().getEndTime(), SysConstants.FORMAT_DATETIME_FULL));
                    //大气压强
                    grib2WdU.setAtmosPressure(new BigInteger( String.valueOf(iCurAtoms)) );
                    //经度
                    grib2WdU.setLon(new BigDecimal(lon));
                    //纬度
                    grib2WdU.setLat(new BigDecimal(lat));

                    Grib2WdInfo grib2WdV = null;
                    grib2WdV = (Grib2WdInfo) grib2WdU.clone();

                    // 风U分量
                    grib2WdU.setWindType("UGRD");
                    grib2WdU.setWindVel(new BigDecimal(arrGlobalPointUV[iCoordinatesPointNum]));

                    // 风V分量
                    // 根据Skyview项目中的Grib2数据UV文件特点：先存U，后存V；  iCoordinatesSum
                    grib2WdV.setWindType("VGRD");
                    grib2WdV.setWindVel(new BigDecimal(arrGlobalPointUV[iCoordinatesPointNum + iCoordinatesSum]));

                    grib2InfoList.add(grib2WdU);
                    grib2InfoList.add(grib2WdV);
//                }
                // 重新赋初值(赋完初值后，循环立即进行 -180 + 1.25 = -178.75)
                if (180 == lon) {
                    lon = -180;
                }
                // 重新设置内循环退出条件（赋值后，仍满足循环条件）
                if(-1.25 == lon) {
                    iCoordinatesPointNum++;
                    break;
                }
                // 这个值是的大小对for循环执行次数的统计呦~~这个执行次数对应着数组中坐标点值的位置
                iCoordinatesPointNum++;
            } //  for (double lon = 0; lon <= 180; lon += 1.25) {
        } //  for (double lat = 90; lat >= -90; lat -= 1.25) {
//        logger.debug("准备数据入库，数量为：" + grib2InfoList.size());
//        if (0 != grib2InfoList.size()) {
//            //                    System.out.println("本次入库的第一数据为：" + grib2InfoList.get(0));
//            ArrayList dataList = (ArrayList) grib2InfoList.clone();
//            grib2InfoList.clear();
//            // 一旦程序执行到这里，grib2TempVo 和 grib2UVVo的Grib2时间参数是一致的
//            SaveGrib2DataThread saveGrib2DataThread = new SaveGrib2DataThread(grib2InfoService, dataList,
//                    grib2TempVo.getGrib2Time(), iCurAtoms);
//            pool.execute(saveGrib2DataThread);
//        }
        if (0 != grib2InfoList.size()) {
            ArrayList dataList = (ArrayList) grib2InfoList.clone();
            grib2InfoList.clear();
            indb(grib2InfoService, dataList, grib2TempVo.getGrib2Time(), iCurAtoms);
        }
    }

    private void indb(Grib2InfoService grib2InfoService, List dataList, Grib2TimeVo grib2TimeVo, final int iCurAtoms){

        logger.info("(开始)入库线程名:" + Thread.currentThread() + DateUtil.getCurrentDateTime(SysConstants.FORMAT_DATETIME_FULL));
        long hasRecordNum = grib2InfoService.countByTimeAlt(grib2TimeVo.getPublicTime(), grib2TimeVo.getEndTime(), iCurAtoms);

        if (0 == hasRecordNum) {
            //保存grib2数据
            grib2InfoService.save(dataList);
            logger.debug("本次入库数据，库中不存在，入库" + dataList.size() + "条");
        } else if (125280 != hasRecordNum) {
            // 删除该时段、该高度层的不合法风温数据
            long delSize = grib2InfoService.deleteByTimeAlt(grib2TimeVo.getPublicTime(), grib2TimeVo.getEndTime(), iCurAtoms);
            grib2InfoService.save(dataList);
            logger.debug("本次入库数据，库中不完整（多or少），已删除" + delSize + "条; 已入库" + dataList.size() + "条" );
        } else if (125280 == hasRecordNum) {
            // 不用进行任何操作
            logger.debug("本次入库数据，库中已存在，不执行入库操作" );
        }
        logger.debug("(结束)入库线程名:" + Thread.currentThread() + DateUtil.getCurrentDateTime(SysConstants.FORMAT_DATETIME_FULL));
    }

    /**
     * Function : 判断当前目录是否需要扫描并录入数据(只有文件名日期在20小时以内的才会录入)
     * param dirName : 以yyyyMMddHH表示的，时间字符串
     * return : < 0 目标目录数据是20小时之前的无效目录
     *          = 0 目标目录数据是20小时那一时刻的有效目录
     *          > 0 目标目录数据是20小时以内的有效目录
     */
    private int isNeedScanDir(String dirName) {
//        String dirName = "2014110205";
//        Date date = DateUtil.StrToDate(str, "yyyyMMddHH");
        try {
            // 目标目录转化称呼
            dirName = DateUtil.changeHour(dirName, 20, SysConstants.FORMAT_DATETIME_PURE_H);
        } catch (ParseException e) {
            logger.error("目录名异常无法解析：" + dirName);
            return -1;
//            e.printStackTrace();
        }
        String curUtcTimeStr = DateUtil.getCurUTCtime(SysConstants.FORMAT_DATETIME_PURE_H);
//        System.out.println(dirName.compareTo(urTimeStr));
        return dirName.compareTo(curUtcTimeStr);
    }


    /**
     * @param filePath : 带解析文件路径（文件中除开始几行为字段说明行外，其余应该是由空白分隔的数据项）
     * @param iStartLineNum : 从第几行开始为实际数据
     * @return Grib2BySkyviewFileVo : 符合条件Grib2文件对象
     * @desc : 找到一个，进行解析（日期、数据排列格式、数据）后删除
     */
    private Grib2BySkyviewFileVo GetGrib2FileObj(String filePath, int iStartLineNum) {
        Grib2BySkyviewFileVo grib2BySkyviewFileVo = null;
        try{
//            logger.debug(filePath);
            File fSacnDir = new File(filePath);
            if ( (null == fSacnDir) || !fSacnDir.isDirectory()) {
                logger.error("无法打开指定数据源(风温or风分量)目录：" + filePath);
                return null;
            }

            String[] fileNameList = fSacnDir.list();
            for (int i = 0; i < fileNameList.length; i++) {
                Grib2TimeVo grib2TimeVo = fileNameToGrib2Time(fileNameList[i]);
                if (null == grib2TimeVo) {
                    continue;
                }
                grib2BySkyviewFileVo = new Grib2BySkyviewFileVo();
                grib2BySkyviewFileVo.setGrib2Time(grib2TimeVo);

                File fDataSource = new File(fSacnDir + File.separator + fileNameList[i]);
                if (!fDataSource.exists() || fDataSource.isDirectory()) {
                    logger.error("文件不存在：" + fDataSource.getName());
                } else {
                    BufferedReader bufReader = new BufferedReader(new FileReader(fDataSource));
                    String strLineTemp = bufReader.readLine();
                    //String strText = "";
                    sb.delete(0,sb.length());
                    int count = 0;
                    while (!ObjectUtil.isBlank(strLineTemp)) {
                        count++;
                        if (count < iStartLineNum) {
                            strLineTemp = bufReader.readLine();
                            if (2 == iStartLineNum) {
                                grib2BySkyviewFileVo.setStrFileFormatDesc(strLineTemp);
                            }
                            continue;
                        }
                        //strText += " " + strLineTemp;
                        sb.append(" ");
                        sb.append(strLineTemp);

                        strLineTemp = bufReader.readLine();
                    }
                    grib2BySkyviewFileVo.setGrib2Context(sb.toString().trim());

                    // 删除移动前，一定要先关闭读写流
                    try {
                        if (bufReader != null)
                            bufReader.close();
                    } catch (IOException ex) {
                        logger.error("流关闭失败");
                        return null;
                    }

//                    if (!FileUtil.MoveFile(fDataSource, backupGrib2SuccessDir)) {
                    if (!fDataSource.delete()) {
                        logger.error(fDataSource.getAbsolutePath() + "解析成功的Skyview格式Grib2文件删除失败，本服务停止！");
                        return null;
                    }
                    // 一次只处理一个文件
                    break;
                } // else
            } // for (int i = 0; i < fileNameList.length; i++) {
        } catch (Exception e) {
            logger.error(e.getMessage());
            return null;
        }

        return grib2BySkyviewFileVo;
    } //  private String[] GetFileContent(String filePath, int iStartLineNum) {

    /**
     * @desc : 解析符合正则表达式的目标数据源文件名
     * @param fileName : 带解析文件路径
     * @return String : 时间字符串 2014-10-16 18:00:00
     */
    private Grib2TimeVo fileNameToGrib2Time(String fileName) {
        Grib2TimeVo grib2TimeVo = null;

        Pattern pattern = Pattern.compile(regexGrib2VaildTime);
        Matcher matcher = pattern.matcher(fileName.trim()); // rawMsg
        if (matcher.matches()) {
            logger.debug("    符合处理条件解析文件：" + fileName);
            String publicTime = "20" + fileName.substring(0, 2) + "-" + fileName.substring(2, 4) + "-" + fileName.substring(4, 6)
                    + " " + fileName.substring(6, 8) + ":00:00";
            try {
                grib2TimeVo = new Grib2TimeVo(publicTime, "",  Integer.valueOf(matcher.group(1)));
            } catch (Exception e) {
                logger.error("Grib2文件名中的有效时间解析 Integer.parseInt(strVaildTime) 失败：" + fileName);
                return null;
            }
        }
        return grib2TimeVo;
    }

    public static void main(String args[]) throws ParseException {
        //        Grib2Controller aaa = new Grib2Controller();
        System.out.println(DateUtil.getCurUTCtime(SysConstants.FORMAT_DATETIME_FULL));
        //        System.out.println(aaa.windCalculation("P202", GribUtil.fl2hpa(BigDecimal.valueOf(18000)).doubleValue()));
        System.out.println(DateUtil.getCurrentDateTime(SysConstants.FORMAT_DATETIME_FULL));

//        fileNameToGrib2Time("14102218.018");
//        ScanDir("C:\\Users\\GuoXY\\Desktop\\FlyPlanData\\micapsdata");
        //            System.out.println(path);



    }
}
